// 版权归Go作者所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// TODO（gri）：这个文件和文件src/cmd/gofmt/internal。go是
// 相同（但对于此注释和包名）。不要修改
// 一个没有另一个。确定我们是否可以在公共API中排除
// 的功能。另请参见#11844了解上下文。

package format

import (
	"bytes"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"strings"
)

// parse将从命名文件中读取的src解析为Go源文件、声明或语句列表。
func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
	file *ast.File,
	sourceAdj func(src []byte, indent int) []byte,
	indentAdj int,
	err error,
) {
	// 作为整个源文件尝试。
	file, err = parser.ParseFile(fset, filename, src, parserMode)
	// 如果没有错误，返回。如果错误是源文件不是以
	// 包行开头，并且源片段没有问题，那么请改为
	// 尝试作为源片段。停止并返回任何其他错误。
	if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") {
		return
	}

	// 如果这是一个声明列表，请通过插入一个package子句将其设置为源文件
	// 。
	// 使用“；”插入，不是换行符，因此psrc中的行号
	// 与src中的行号匹配。
	psrc := append([]byte("package p;"), src...)
	file, err = parser.ParseFile(fset, filename, psrc, parserMode)
	if err == nil {
		sourceAdj = func(src []byte, indent int) []byte {
			// 删除package子句。
			// Gofmt已将“；”打开变成“\n”。
			src = src[indent+len("package p\n"):]
			return bytes.TrimSpace(src)
		}
		return
	}
	// 如果错误是源文件不是以
	// 声明开头，请尝试将其作为语句列表。
	// 停止并返回任何其他错误。
	if !strings.Contains(err.Error(), "expected declaration") {
		return
	}

	// 如果这是一个语句列表，则通过插入一个package子句并将列表
	// 转换为一个函数体，使其成为一个源文件。这也处理表达式。
	// 使用“；”插入，不是换行符，因此fsrc中的行号
	// 与src中的行号匹配。在“}”之前添加一个额外的“\n”
	// 以确保在“}”之前刷新注释。
	fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}')
	file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
	if err == nil {
		sourceAdj = func(src []byte, indent int) []byte {
			// 上限调整缩进为零。
			if indent < 0 {
				indent = 0
			}
			// 取下包装。
			// Gofmt已将“；”变成“\n\n”。
			// 将有两个带缩进的非空行，因此缩进为2*。
			src = src[2*indent+len("package p\n\nfunc _() {"):]
			// 仅删除“}\n”后缀：剩余的空格将被修剪
			src = src[:len(src)-len("}\n")]
			return bytes.TrimSpace(src)
		}
		// Gofmt还将函数体缩进了一级。
		// 用缩进调整。
		indentAdj = -1
	}

	// 成功，或无选择。
	return
}

// 格式化最初从src 
// 获取的给定包文件，并通过sourceAdj 
// 和indentAdj根据原始源调整结果。
func format(
	fset *token.FileSet,
	file *ast.File,
	sourceAdj func(src []byte, indent int) []byte,
	indentAdj int,
	src []byte,
	cfg printer.Config,
) ([]byte, error) {
	if sourceAdj == nil {
		// 完整的源文件。
		var buf bytes.Buffer
		err := cfg.Fprint(&buf, fset, file)
		if err != nil {
			return nil, err
		}
		return buf.Bytes(), nil
	}

	// 部分源文件。
	// 确定并预先设置前导空间。
	i, j := 0, 0
	for j < len(src) && isSpace(src[j]) {
		if src[j] == '\n' {
			i = j + 1 // 前导空间中最后一行的字节偏移量
		}
		j++
	}
	var res []byte
	res = append(res, src[:i]...)

	// 确定第一行代码的缩进并在缩进前加上前缀。
	// 除非没有制表符，否则将忽略空格，在这种情况下，空格将作为一个制表符计算。
	indent := 0
	hasSpace := false
	for _, b := range src[i:j] {
		switch b {
		case ' ':
			hasSpace = true
		case '\t':
			indent++
		}
	}
	if indent == 0 && hasSpace {
		indent = 1
	}
	for i := 0; i < indent; i++ {
		res = append(res, '\t')
	}

	// 格式化源代码。
	// 写出来时不要有任何前导和尾随空格。
	cfg.Indent = indent + indentAdj
	var buf bytes.Buffer
	err := cfg.Fprint(&buf, fset, file)
	if err != nil {
		return nil, err
	}
	out := sourceAdj(buf.Bytes(), cfg.Indent)

	// 如果调整后的输出为空，则源
	// 为空，但（可能）为空白。
	// 结果是传入源。
	if len(out) == 0 {
		return src, nil
	}

	// 否则，将输出追加到前导空格。
	res = append(res, out...)

	// 确定并追加尾随空格。
	i = len(src)
	for i > 0 && isSpace(src[i-1]) {
		i--
	}
	return append(res, src[i:]...), nil
}

// isSpace报告字节是否为空格字符。
// isSpace将空间定义为以下字节之一：''\t'、'\n'和'\r'。
func isSpace(b byte) bool {
	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
}
